/*!
    \file    hid_keyboard_mouse_core.c
    \brief   custom HID class driver

    \version 2020-08-01, V3.0.0, firmware for GD32F30x
    \version 2021-06-22, V3.0.1, firmware for GD32F30x
    \version 2021-11-09, V3.0.2, firmware for GD32F30x
    \version 2022-06-10, V3.1.0, firmware for GD32F30x
*/

/*
    Copyright (c) 2022, GigaDevice Semiconductor Inc.

    Redistribution and use in source and binary forms, with or without modification, 
are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright notice, this 
       list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice, 
       this list of conditions and the following disclaimer in the documentation 
       and/or other materials provided with the distribution.
    3. Neither the name of the copyright holder nor the names of its contributors 
       may be used to endorse or promote products derived from this software without 
       specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
OF SUCH DAMAGE.
*/
#include "stdio.h"
#include "stdint.h"
#include <string.h>
#include "DevicesQueue.h"
#include "usbd_transc.h"
#include "composite_keyboard_mouse_cdc_msc.h"


extern usb_class usb_class_composite;

usb_dev usbd_composite;


#define USBD_VID                     0x28E9
#define USBD_PID                     0x5678

const uint8_t hid_keyboard_report_desc[] =
{
    0x05, 0x01,  /* USAGE_PAGE (Generic Desktop) */
    0x09, 0x06,  /* USAGE (Keyboard) */
    0xa1, 0x01,  /* COLLECTION (Application) */

    0x05, 0x07,  /* USAGE_PAGE (Keyboard/Keypad) */
    0x19, 0xe0,  /* USAGE_MINIMUM (Keyboard LeftControl) */
    0x29, 0xe7,  /* USAGE_MAXIMUM (Keyboard Right GUI) */
    0x15, 0x00,  /* LOGICAL_MINIMUM (0) */
    0x25, 0x01,  /* LOGICAL_MAXIMUM (1) */
    0x95, 0x08,  /* REPORT_COUNT (8) */
    0x75, 0x01,  /* REPORT_SIZE (1) */
    0x81, 0x02,  /* INPUT (Data,Var,Abs) */

    0x95, 0x01,  /* REPORT_COUNT (1) */
    0x75, 0x08,  /* REPORT_SIZE (8) */
    0x81, 0x03,  /* INPUT (Cnst,Var,Abs) */

    0x95, 0x06,  /* REPORT_COUNT (6) */
    0x75, 0x08,  /* REPORT_SIZE (8) */
    0x15, 0x00,  /* LOGICAL_MINIMUM (0) */
    0x26, 0xFF, 0x00,  /* LOGICAL_MAXIMUM (255) */
    0x05, 0x07,  /* USAGE_PAGE (Keyboard/Keypad) */
    0x19, 0x00,  /* USAGE_MINIMUM (Reserved (no event indicated)) */
    0x29, 0x65,  /* USAGE_MAXIMUM (Keyboard Application) */
    0x81, 0x00,  /* INPUT (Data,Ary,Abs) */

    0xc0         /* END_COLLECTION */
};

const uint8_t hid_mouse_report_desc[] =
{
    0x05, 0x01,  /* USAGE_PAGE (Generic Desktop) */
    0x09, 0x02,  /* USAGE (Mouse) */
    0xa1, 0x01,  /* COLLECTION (Application) */
    0x09, 0x01,  /* USAGE (Pointer) */

    0xa1, 0x00,  /* COLLECTION (Physical) */
    0x05, 0x09,  /* USAGE_PAGE (Button) */
    0x19, 0x01,  /* USAGE_MINIMUM (1) */
    0x29, 0x03,  /* USAGE_MAXIMUM (3) */

    0x15, 0x00,  /* LOGICAL_MINIMUM (0) */
    0x25, 0x01,  /* LOGICAL_MAXIMUM (1) */
    0x95, 0x03,  /* REPORT_COUNT (3) */
    0x75, 0x01,  /* REPORT_SIZE (1) */
    0x81, 0x02,  /* INPUT (Data,Var,Abs) */

    0x95, 0x01,  /* REPORT_COUNT (1) */
    0x75, 0x05,  /* REPORT_SIZE (5) */
    0x81, 0x01,  /* INPUT (Cnst,Var,Abs) */

    0x05, 0x01,  /* USAGE_PAGE (Generic Desktop) */
    0x09, 0x30,  /* USAGE (X) */
    0x09, 0x31,  /* USAGE (Y) */
    0x09, 0x38,  /* USAGE (Wheel) */

    0x15, 0x81,  /* LOGICAL_MINIMUM (-127) */
    0x25, 0x7F,  /* LOGICAL_MAXIMUM (127) */
    0x75, 0x08,  /* REPORT_SIZE (8) */
    0x95, 0x03,  /* REPORT_COUNT (3) */
    0x81, 0x06,  /* INPUT (Data,Var,Rel) */
    0xc0,        /* END_COLLECTION */
    0xc0         /* END_COLLECTION */
};

/* Note:it should use the C99 standard when compiling the below codes */
/* USB standard device descriptor */
usb_desc_dev usb_dev_desc =
{
    .header =
     {
         .bLength          = USB_DEV_DESC_LEN,
         .bDescriptorType  = USB_DESCTYPE_DEV,
     },
    .bcdUSB                = 0x0200U,
    .bDeviceClass          = 0xEFU,
    .bDeviceSubClass       = 0x02U,
    .bDeviceProtocol       = 0x01U,
    .bMaxPacketSize0       = USBD_EP0_MAX_SIZE,
    .idVendor              = USBD_VID,
    .idProduct             = USBD_PID,
    .bcdDevice             = 0x0100U,
    .iManufacturer         = STR_IDX_MFC,
    .iProduct              = STR_IDX_PRODUCT,
    .iSerialNumber         = STR_IDX_SERIAL,
    .bNumberConfigurations = USBD_CFG_MAX_NUM,
};

/* USB device configure descriptor */
usb_desc_config_set_struct usb_config_desc =
{
    .config =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_config),
             .bDescriptorType = USB_DESCTYPE_CONFIG
         },
        .wTotalLength         = sizeof(usb_desc_config_set_struct),
        .bNumInterfaces       = USBD_ITF_MAX_NUM,
        .bConfigurationValue  = 1,          /* 此配置的形参值：usb_req->wValue */
        .iConfiguration       = 0,
        .bmAttributes         = 0x80,
        .bMaxPower            = 50
    },

    .iad_keyboard = 
    {
         .header = 
         {
             .bLength         = sizeof(usb_desc_IAD),
             .bDescriptorType = 0x0B
         },
         .bFirstInterface     = HID_KEYBOARD_INTERFACE,
         .bInterfaceCount     = 1,
         .bFunctionClass      = USB_HID_CLASS,
         .bFunctionSubClass   = USB_HID_SUBCLASS_BOOT_ITF,
         .bFunctionProtocol   = USB_HID_PROTOCOL_KEYBOARD,
         .iFunction           = 0x00U
    },

    .interface_keyboard =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_itf),
             .bDescriptorType = USB_DESCTYPE_ITF
         },
        .bInterfaceNumber     = HID_KEYBOARD_INTERFACE,
        .bAlternateSetting    = 0,
        .bNumEndpoints        = 2,
        .bInterfaceClass      = USB_HID_CLASS,
        .bInterfaceSubClass   = USB_HID_SUBCLASS_BOOT_ITF,
        .bInterfaceProtocol   = USB_HID_PROTOCOL_KEYBOARD,
        .iInterface           = 0
    },

    .vendor_keyboard =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_hid),
             .bDescriptorType = USB_DESCTYPE_HID
         },
        .bcdHID               = 0x0111U,
        .bCountryCode         = 0x00U,
        .bNumDescriptors      = 0x01U,
        .bDescriptorType      = USB_DESCTYPE_REPORT,
        .wDescriptorLength    = sizeof(hid_keyboard_report_desc),
    },

    .epin_keyboard =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_ep),
             .bDescriptorType = USB_DESCTYPE_EP
         },
        .bEndpointAddress     = HID_KEYBOARD_IN_EP,
        .bmAttributes         = USB_EP_ATTR_INT,
        .wMaxPacketSize       = HID_KEYBOARD_IN_PACKET_SIZE,
        .bInterval            = 0x40,     /* x ms轮询一次 */
    },

    .epout_keyboard =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_ep),
             .bDescriptorType = USB_DESCTYPE_EP
         },
        .bEndpointAddress     = HID_KEYBOARD_OUT_EP,
        .bmAttributes         = USB_EP_ATTR_INT,
        .wMaxPacketSize       = HID_KEYBOARD_OUT_PACKET_SIZE,
        .bInterval            = 100,    /* x ms轮询一次 */
    },


    .iad_mouse = 
    {
         .header = 
         {
             .bLength         = sizeof(usb_desc_IAD),
             .bDescriptorType = 0x0B
         },
         .bFirstInterface     = HID_MOUSE_INTERFACE,
         .bInterfaceCount     = 1,
         .bFunctionClass      = USB_HID_CLASS,
         .bFunctionSubClass   = USB_HID_SUBCLASS_BOOT_ITF,
         .bFunctionProtocol   = USB_HID_PROTOCOL_MOUSE,
         .iFunction           = 0x00U
    },

    .interface_mouse =
    {
        .header = 
         {
             .bLength         = sizeof(usb_desc_itf),
             .bDescriptorType = USB_DESCTYPE_ITF
         },
        .bInterfaceNumber     = HID_MOUSE_INTERFACE,
        .bAlternateSetting    = 0,
        .bNumEndpoints        = 1,
        .bInterfaceClass      = USB_HID_CLASS,
        .bInterfaceSubClass   = USB_HID_SUBCLASS_BOOT_ITF,
        .bInterfaceProtocol   = USB_HID_PROTOCOL_MOUSE,
        .iInterface           = 0
    },

    .vendor_mouse = 
    {
        .header = 
         {
             .bLength         = sizeof(usb_desc_hid),
             .bDescriptorType = USB_DESCTYPE_HID 
         },
        .bcdHID               = 0x0111U,
        .bCountryCode         = 0x00U,
        .bNumDescriptors      = 0x01U,
        .bDescriptorType      = USB_DESCTYPE_REPORT,
        .wDescriptorLength    = sizeof(hid_mouse_report_desc),
    },

    .epin_mouse = 
    {
        .header = 
         {
             .bLength         = sizeof(usb_desc_ep), 
             .bDescriptorType = USB_DESCTYPE_EP 
         },
        .bEndpointAddress     = HID_MOUSE_IN_EP,
        .bmAttributes         = USB_EP_ATTR_INT,
        .wMaxPacketSize       = HID_MOUSE_IN_PACKET_SIZE,
        .bInterval            = 10,     /* x ms轮询一次 */
    },


    .iad_cdc = 
    {
         .header = 
         {
             .bLength         = sizeof(usb_desc_IAD),
             .bDescriptorType = 0x0B
         },
         .bFirstInterface     = CDC_ACM_CMD_INTERFACE,
         .bInterfaceCount     = 2,
         .bFunctionClass      = USB_CLASS_CDC,
         .bFunctionSubClass   = USB_CDC_SUBCLASS_ACM,
         .bFunctionProtocol   = USB_CDC_PROTOCOL_AT,
         .iFunction           = 0x00U
    },

    .interface_cdc_cmd =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_itf),
             .bDescriptorType = USB_DESCTYPE_ITF
         },
        .bInterfaceNumber     = CDC_ACM_CMD_INTERFACE,
        .bAlternateSetting    = 0,
        .bNumEndpoints        = 1,
        .bInterfaceClass      = USB_CLASS_CDC,
        .bInterfaceSubClass   = USB_CDC_SUBCLASS_ACM,
        .bInterfaceProtocol   = USB_CDC_PROTOCOL_AT,
        .iInterface           = 0
    },

    .cdc_header =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_header_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
         },
        .bDescriptorSubtype  = 0x00U,
        .bcdCDC              = 0x0110U
    },

    .cdc_call_managment =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_call_managment_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
         },
        .bDescriptorSubtype  = 0x01U,
        .bmCapabilities      = USB_CDC_PROTOCOL_NONE,
        .bDataInterface      = CDC_ACM_DATA_INTERFACE
    },

    .cdc_acm =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_acm_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
         },
        .bDescriptorSubtype  = 0x02U,
        .bmCapabilities      = 0x02U,
    },

    .cdc_union =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_union_func),
            .bDescriptorType = USB_DESCTYPE_CS_INTERFACE
         },
        .bDescriptorSubtype  = 0x06U,
        .bMasterInterface    = CDC_ACM_CMD_INTERFACE,
        .bSlaveInterface0    = CDC_ACM_CMD_INTERFACE + 1,
    },

    .epin_cdc_cmd =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_ep),
            .bDescriptorType = USB_DESCTYPE_EP,
         },
        .bEndpointAddress    = CDC_ACM_CMD_EP,
        .bmAttributes        = USB_EP_ATTR_INT,
        .wMaxPacketSize      = CDC_ACM_CMD_PACKET_SIZE,
        .bInterval           = 0x0A
    },


    .interface_cdc_data =
    {
        .header =
         {
            .bLength         = sizeof(usb_desc_itf),
            .bDescriptorType = USB_DESCTYPE_ITF,
         },
        .bInterfaceNumber    = CDC_ACM_DATA_INTERFACE,
        .bAlternateSetting   = 0,
        .bNumEndpoints       = 2,
        .bInterfaceClass     = USB_CLASS_DATA,
        .bInterfaceSubClass  = 0x00U,
        .bInterfaceProtocol  = USB_CDC_PROTOCOL_NONE,
        .iInterface          = 0
    },

    .epout_cdc_data =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_ep),
             .bDescriptorType = USB_DESCTYPE_EP,
         },
        .bEndpointAddress     = CDC_ACM_OUT_EP,
        .bmAttributes         = USB_EP_ATTR_BULK,
        .wMaxPacketSize       = CDC_ACM_DATA_PACKET_SIZE,
        .bInterval            = 0x00U
    },

    .epin_cdc_data =
    {
        .header =
         {
             .bLength         = sizeof(usb_desc_ep),
             .bDescriptorType = USB_DESCTYPE_EP
         },
        .bEndpointAddress     = CDC_ACM_IN_EP,
        .bmAttributes         = USB_EP_ATTR_BULK,
        .wMaxPacketSize       = CDC_ACM_DATA_PACKET_SIZE,
        .bInterval            = 0x00U
    },


    .iad_msc = 
    {
         .header = 
         {
             .bLength         = sizeof(usb_desc_IAD),
             .bDescriptorType = 0x0B
         },
         .bFirstInterface     = 4,
         .bInterfaceCount     = 1,
         .bFunctionClass      = USB_CLASS_MSC,
         .bFunctionSubClass   = USB_MSC_SUBCLASS_SCSI,
         .bFunctionProtocol   = USB_MSC_PROTOCOL_BBB,
         .iFunction           = 0x00U
    },

    .interface_msc = 
    {
        .header = {
            .bLength         = sizeof(usb_desc_itf), 
            .bDescriptorType = USB_DESCTYPE_ITF
        },
        .bInterfaceNumber    = USBD_MSC_INTERFACE,
        .bAlternateSetting   = 0,
        .bNumEndpoints       = 2,
        .bInterfaceClass     = USB_CLASS_MSC,
        .bInterfaceSubClass  = USB_MSC_SUBCLASS_SCSI,
        .bInterfaceProtocol  = USB_MSC_PROTOCOL_BBB,
        .iInterface          = 0x00U
    },

    .epin_msc = 
    {
        .header = {
            .bLength         = sizeof(usb_desc_ep), 
            .bDescriptorType = USB_DESCTYPE_EP
        },
        .bEndpointAddress    = MSC_IN_EP,
        .bmAttributes        = USB_EP_ATTR_BULK,
        .wMaxPacketSize      = MSC_EPIN_SIZE,
        .bInterval           = 0x00U
    },

    .epout_msc = 
    {
        .header = {
            .bLength         = sizeof(usb_desc_ep), 
            .bDescriptorType = USB_DESCTYPE_EP
        },
        .bEndpointAddress    = MSC_OUT_EP,
        .bmAttributes        = USB_EP_ATTR_BULK,
        .wMaxPacketSize      = MSC_EPOUT_SIZE,
        .bInterval           = 0x00U
    }
};

/* USB language ID descriptor */
const static usb_desc_LANGID usbd_language_id_desc =
{
    .header =
     {
         .bLength = sizeof(usb_desc_LANGID),
         .bDescriptorType = USB_DESCTYPE_STR
     },
    .wLANGID = ENG_LANGID
};

/* USB manufacture string */
const static usb_desc_str manufacturer_string =
{
    .header =
     {
         .bLength         = USB_STRING_LEN(10U),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
    .unicode_string = {'G', 'i', 'g', 'a', 'D', 'e', 'v', 'i', 'c', 'e'}
};

/* USB product string */
const static usb_desc_str product_string =
{
    .header = 
     {
         .bLength         = USB_STRING_LEN(9),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
    .unicode_string = {'C', 'o', 'm', 'p', 'o', 's', 'i', 't', 'e'}
};

/* USB serial string */
static usb_desc_str serial_string =
{
    .header =
     {
         .bLength         = USB_STRING_LEN(12),
         .bDescriptorType = USB_DESCTYPE_STR,
     },
};

/* USB string descriptor set */
uint8_t* usbd_strings[] =
{
    [STR_IDX_LANGID]  = (uint8_t *)&usbd_language_id_desc,
    [STR_IDX_MFC]     = (uint8_t *)&manufacturer_string,
    [STR_IDX_PRODUCT] = (uint8_t *)&product_string,
    [STR_IDX_SERIAL]  = (uint8_t *)&serial_string,
};

usb_desc hid_desc = {
    .dev_desc    = (uint8_t *)&usb_dev_desc,
    .config_desc = (uint8_t *)&usb_config_desc,
    .strings     = usbd_strings
};

/*!
    \brief      initialize the HID device
    \param[in]  udev: pointer to USB device instance
    \param[in]  config_index: configuration index
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t usb_init (usb_dev *udev, uint8_t config_index)
{
    static usb_hid_keyboard_handler hid_keyboard;
    static usb_hid_mouse_handler hid_mouse;
    static usb_cdc_acm_handler cdc_acm;
    static usbd_msc_handler msc_handler;


    /* deinitialize HID endpoints */
    memset((void *)&hid_keyboard, 0, sizeof(usb_hid_keyboard_handler));

    /* initialize Tx endpoint */
    usbd_ep_init(udev, EP_BUF_SNG, HID_KEYBOARD_INT_TX_ADDR, &(usb_config_desc.epin_keyboard));

    udev->ep_transc[EP_ID(HID_KEYBOARD_IN_EP)][TRANSC_IN] = usb_class_composite.data_in;

    hid_keyboard.prev_transfer_complete = 1U;

    udev->class_data[HID_KEYBOARD_INTERFACE] = (void *)&hid_keyboard;


    memset((void *)&hid_mouse, 0, sizeof(usb_hid_mouse_handler));

    /* initialize Tx endpoint */
    usbd_ep_init(udev, EP_BUF_SNG, HID_MOURCE_INT_TX_ADDR, &(usb_config_desc.epin_mouse));

    udev->ep_transc[EP_ID(HID_MOUSE_IN_EP)][TRANSC_IN] = usb_class_composite.data_in;

    hid_mouse.prev_transfer_complete = 1U;

    udev->class_data[HID_MOUSE_INTERFACE] = (void *)&hid_mouse;


    if (NULL != udev->user_data) {
        ((mice_fop_handler *)(udev->user_data))->mice_itf_config();
    }


    memset((void *)&cdc_acm, 0, sizeof(usb_cdc_acm_handler));

    /* initialize the data endpoints */
    usbd_ep_init(udev, EP_BUF_SNG, CDC_ACM_BULK_TX_ADDR, &(usb_config_desc.epin_cdc_data));
    usbd_ep_init(udev, EP_BUF_SNG, CDC_ACM_BULK_RX_ADDR, &(usb_config_desc.epout_cdc_data));

    /* initialize the command endpoint */
    usbd_ep_init(udev, EP_BUF_SNG, CDC_ACM_INT_TX_ADDR, &(usb_config_desc.epin_cdc_cmd));

    udev->ep_transc[EP_ID(CDC_ACM_IN_EP)][TRANSC_IN] = usb_class_composite.data_in;
    udev->ep_transc[EP_ID(CDC_ACM_OUT_EP)][TRANSC_OUT] = usb_class_composite.data_out;

    /* initialize cdc handler structure */
    cdc_acm.send_ing = 0;

    cdc_acm.line_coding = (acm_line){
        .dwDTERate   = 115200,
        .bCharFormat = 0,
        .bParityType = 0,
        .bDataBits   = 8
    };

    udev->class_data[CDC_ACM_CMD_INTERFACE] = (void *)&cdc_acm;


    memset((void *)&msc_handler, 0U, sizeof(usbd_msc_handler));

    udev->class_data[USBD_MSC_INTERFACE] = (void *)&msc_handler;

    /* initialize Tx endpoint */
    usbd_ep_init(udev, EP_BUF_SNG, MSC_BULK_TX_ADDR, &(usb_config_desc.epin_msc));

    /* initialize Rx endpoint */
    usbd_ep_init(udev, EP_BUF_SNG, MSC_BULK_RX_ADDR, &(usb_config_desc.epout_msc));

    udev->ep_transc[EP_ID(MSC_IN_EP)][TRANSC_IN] = usb_class_composite.data_in;
    udev->ep_transc[EP_ID(MSC_OUT_EP)][TRANSC_OUT] = usb_class_composite.data_out;

    /* initialize the BBB layer */
    msc_bbb_init(udev);

    return USBD_OK;
}

/*!
    \brief      de-initialize the HID device
    \param[in]  udev: pointer to USB device instance
    \param[in]  config_index: configuration index
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t usb_deinit (usb_dev *udev, uint8_t config_index)
{
    /* deinitialize HID endpoints */
    usbd_ep_deinit(udev, HID_KEYBOARD_IN_EP);
    usbd_ep_deinit(udev, HID_KEYBOARD_OUT_EP);
    usbd_ep_deinit(udev, HID_MOUSE_IN_EP);

    /* deinitialize the data endpoints */
    usbd_ep_deinit(udev, CDC_ACM_IN_EP);
    usbd_ep_deinit(udev, CDC_ACM_OUT_EP);

    /* deinitialize the command endpoint */
    usbd_ep_deinit(udev, CDC_ACM_CMD_EP);

    /* clear MSC endpoints */
    usbd_ep_deinit (udev, MSC_IN_EP);
    usbd_ep_deinit (udev, MSC_OUT_EP);

    /* deinitialize the BBB layer */
    msc_bbb_deinit(udev);

    return USBD_OK;
}

/*!
    \brief      command data received on control endpoint
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t usb_ctlx_out (usb_dev *udev)
{
    if (NO_CMD != udev->class_core->req_cmd)
    {
        udev->class_core->req_cmd = NO_CMD;
    }

    return USBD_OK;
}

/*!
    \brief      handle the HID class-specific requests
    \param[in]  udev: pointer to USB device instance
    \param[in]  req: device class-specific request
    \param[out] none
    \retval     USB device operation status
*/
static uint8_t usb_req_handler (usb_dev *udev, usb_req *req)
{
    static uint8_t usbd_msc_maxlun = 0;
    usb_hid_keyboard_handler *hid = (usb_hid_keyboard_handler *)udev->class_data[HID_KEYBOARD_INTERFACE];
    usb_cdc_acm_handler *cdc_acm = (usb_cdc_acm_handler *)udev->class_data[CDC_ACM_CMD_INTERFACE];
    uint8_t status = USBD_OK;

    switch (req->bRequest)
    {
        case SET_REPORT: break;

        case GET_IDLE: usb_transc_config(&udev->transc_in[0], (uint8_t *)&hid->idle_state, 1, 0); break;

        case SET_IDLE: hid->idle_state = (uint8_t)(req->wValue >> 8); break;

        case GET_PROTOCOL: usb_transc_config(&udev->transc_in[0], (uint8_t *)&hid->protocol, 1, 0); break;

        case SET_PROTOCOL: hid->protocol = (uint8_t)(req->wValue); break;

        case USB_GET_DESCRIPTOR:
            if (USB_DESCTYPE_REPORT == (req->wValue >> 8))
            {
                if(req->wIndex == HID_KEYBOARD_INTERFACE)
                    usb_transc_config(&udev->transc_in[0], (uint8_t *)hid_keyboard_report_desc, USB_MIN(sizeof(hid_keyboard_report_desc), req->wLength), 0);
                else if(req->wIndex == HID_MOUSE_INTERFACE)
                    usb_transc_config(&udev->transc_in[0], (uint8_t *)hid_mouse_report_desc, USB_MIN(sizeof(hid_mouse_report_desc), req->wLength), 0);
            }
            else if(USB_DESCTYPE_HID == (req->wValue >> 8))
            {
                if(req->wIndex == HID_KEYBOARD_INTERFACE)
                    usb_transc_config(&udev->transc_in[0], (uint8_t *)(&(usb_config_desc.vendor_keyboard)), USB_MIN(9, req->wLength), 0);
                else if(req->wIndex == HID_MOUSE_INTERFACE)
                    usb_transc_config(&udev->transc_in[0], (uint8_t *)(&(usb_config_desc.vendor_mouse)), USB_MIN(9, req->wLength), 0);
            }
            break;

        /* CDC ACM 部分 */
        case GET_LINE_CODING: usb_transc_config(&udev->transc_in[0], (uint8_t *)&cdc_acm->line_coding, 7, 0); break;

        case SET_LINE_CODING:
            /* set the value of the current command to be processed */
            udev->class_core->req_cmd = req->bRequest;
            usb_transc_config(&udev->transc_out[0], (uint8_t *)&cdc_acm->line_coding, req->wLength, 0);

            /* 串口状态更改后，需要设置接收缓存，以准备数据接收 */
            cdc_acm->send_ing = 0;
            usbd_ep_recev(udev, CDC_ACM_OUT_EP, cdc_acm->data_receive, sizeof(cdc_acm->data_receive));
            break;

        case SET_CONTROL_LINE_STATE: break;

        /* MSC 部分 */
        case BBB_GET_MAX_LUN :
            if((0 == req->wValue) &&  (1 == req->wLength) && (0x80 == (req->bmRequestType & 0x80)))
            {
                usbd_msc_maxlun = (uint8_t)usbd_mem_fops->mem_maxlun();
                usb_transc_config(&udev->transc_in[0], &usbd_msc_maxlun, 1, 0);
            }
            else
            {
                status =  USBD_FAIL;
            }
            break;

        case BBB_RESET :
            if((0 == req->wValue) &&  (0 == req->wLength) && (0x80 != (req->bmRequestType & 0x80)))
            {
                msc_bbb_reset(udev);
            }
            else
            {
                status =  USBD_FAIL;
            }
            break;

        case USB_CLEAR_FEATURE: msc_bbb_clrfeature (udev, (uint8_t)req->wIndex); break;

        default : status = USBD_FAIL; break;
    }

    return status;
}

/*!
    \brief      handle custom HID data in transaction
    \param[in]  udev: pointer to USB device instance
    \param[in]  ep_num: endpoint number
    \param[out] none
    \retval     none
*/
static void usb_data_in (usb_dev *udev, uint8_t ep_num)
{
    usb_hid_keyboard_handler *hid_keyboard;
    usb_hid_mouse_handler *hid_mouse;
    usb_cdc_acm_handler *cdc_acm;
    usb_transc *transc;

    switch(ep_num)
    {
        case EP_ID(HID_MOUSE_IN_EP) :
            hid_mouse = (usb_hid_mouse_handler *)udev->class_data[HID_MOUSE_INTERFACE];
            if((hid_mouse->data[0] == 0) && (hid_mouse->data[1] == 0) && (hid_mouse->data[2] == 0) && (hid_mouse->data[3] == 0))
            {
                hid_mouse->prev_transfer_complete = 1U;
            }
            else
            {
                hid_mouse->data[0] = hid_mouse->data[1] = hid_mouse->data[2] = hid_mouse->data[3] = 0;
                usbd_ep_send(udev, ep_num, hid_mouse->data, HID_MOUSE_IN_PACKET_SIZE);
            }
        break;

        case EP_ID(HID_KEYBOARD_IN_EP) :
            hid_keyboard = (usb_hid_keyboard_handler *)udev->class_data[HID_KEYBOARD_INTERFACE];
            if(hid_keyboard->data[2] == 0U)
            {
                hid_keyboard->prev_transfer_complete = 1U;
            }
            else
            {
                hid_keyboard->data[2] = 0U;
                usbd_ep_send(udev, ep_num, hid_keyboard->data, HID_KEYBOARD_IN_PACKET_SIZE);
            }
        break;

        case EP_ID(CDC_ACM_IN_EP) :
            if(cdc_acm_datas_send(NULL, 0) <= 0)
            {
                /* USB协议规定：最后一帧长度等于端点长度时，需要再发送一次0字节的帧 */
                transc = &udev->transc_in[EP_ID(ep_num)];
                if (transc->xfer_count == transc->max_len)
                {
                    usbd_ep_send(udev, EP_ID(ep_num), NULL, 0U);
                }
                else
                {
                    cdc_acm = (usb_cdc_acm_handler *)udev->class_data[CDC_ACM_CMD_INTERFACE];
                    cdc_acm->send_ing = 0;
                }
            }
        break;

        case EP_ID(MSC_IN_EP) : msc_bbb_data_in (udev, ep_num); break;

        default : break;
    }
}

/*!
    \brief      handle custom HID data out transaction
    \param[in]  udev: pointer to USB device instance
    \param[in]  ep_num: endpoint number
    \param[out] none
    \retval     none
*/
static void usb_data_out (usb_dev *udev, uint8_t ep_num)
{
    usb_cdc_acm_handler *cdc_acm;

    if (EP_ID(CDC_ACM_OUT_EP) == ep_num)
    {
        cdc_acm = (usb_cdc_acm_handler *)udev->class_data[CDC_ACM_CMD_INTERFACE];

        /* 把接收到的数据转移到FIFO */
        enumQueuePushDatas(&g_TypeQueueUsbCdc1AcmRead, cdc_acm->data_receive, udev->transc_out[EP_ID(ep_num)].xfer_count);

        /* 设置下次接收参数 */
        usbd_ep_recev(udev, ep_num, cdc_acm->data_receive, sizeof(cdc_acm->data_receive));
    }
    else if (EP_ID(MSC_OUT_EP) == ep_num)
    {
        msc_bbb_data_out (udev, ep_num);
    }
}

usb_class usb_class_composite = {
    .req_cmd       = NO_CMD,

    .init          = usb_init,
    .deinit        = usb_deinit,
    .req_process   = usb_req_handler,
    .ctlx_out      = usb_ctlx_out,
    .data_in       = usb_data_in,
    .data_out      = usb_data_out
};

void hid_keyboard_data_send(uint8_t ucBytes[HID_KEYBOARD_IN_PACKET_SIZE])
{
    usb_hid_keyboard_handler *hid_keyboard = (usb_hid_keyboard_handler *)usbd_composite.class_data[HID_KEYBOARD_INTERFACE];

    if (hid_keyboard->prev_transfer_complete)
    {
        memcpy(hid_keyboard->data, ucBytes, sizeof(hid_keyboard->data));

        hid_keyboard->prev_transfer_complete = 0U;

        usbd_ep_send(&usbd_composite, HID_KEYBOARD_IN_EP, hid_keyboard->data, HID_KEYBOARD_IN_PACKET_SIZE);
    }
}

void hid_mouse_data_send(uint8_t ucButtons, int8_t cX, int8_t cY, int8_t cWhell)
{
    usb_hid_mouse_handler *hid_mouse = (usb_hid_mouse_handler *)usbd_composite.class_data[HID_MOUSE_INTERFACE];

    if (hid_mouse->prev_transfer_complete)
    {
        hid_mouse->data[0] = ucButtons;
        hid_mouse->data[1] = cX;
        hid_mouse->data[2] = cY;
        hid_mouse->data[3] = cWhell;

        hid_mouse->prev_transfer_complete = 0U;

        usbd_ep_send(&usbd_composite, HID_MOUSE_IN_EP, hid_mouse->data, HID_MOUSE_IN_PACKET_SIZE);
    }
}

/*!
    \brief      receive CDC ACM data
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     none
*/
int32_t cdc_acm_datas_receive(void *pvBuff, int32_t iLength)
{
    int32_t iLengthNow = 0;

    if((iLengthNow = iQueueGetLengthOfOccupy(&g_TypeQueueUsbCdc1AcmRead)) > 0)
    {
        iLengthNow = iLengthNow < iLength ? iLengthNow : iLength;

        enumQueuePopDatas(&g_TypeQueueUsbCdc1AcmRead, pvBuff, iLengthNow);
    }

    return iLengthNow;
}

/*!
    \brief      send CDC ACM data
    \param[in]  udev: pointer to USB device instance
    \param[out] none
    \retval     none
*/
int32_t cdc_acm_datas_send (void *pvBuff, int32_t iLength)
{
    usb_cdc_acm_handler *cdc_acm = (usb_cdc_acm_handler *)usbd_composite.class_data[CDC_ACM_CMD_INTERFACE];
    uint8_t data_send[CDC_ACM_DATA_PACKET_SIZE];

    if(cdc_acm == NULL)
        return 0;

    /* 先把数据存放到FIFO */
    if((pvBuff != NULL) && (iLength > 0))
    {
        enumQueuePushDatas(&g_TypeQueueUsbCdc1AcmSend, pvBuff, iLength);

        if(cdc_acm->send_ing != 0)
            return 0;
    }

    /* 后续再从FIFO中取出来，以实现调用USB发送函数不阻塞的功能 */
    if((iLength = iQueueGetLengthOfOccupy(&g_TypeQueueUsbCdc1AcmSend)) > 0)
    {
        iLength = iLength < sizeof(data_send) ? iLength : sizeof(data_send);

        enumQueuePopDatas(&g_TypeQueueUsbCdc1AcmSend, data_send, iLength);

        /* 设置发送参数 */
        cdc_acm->send_ing = 1;
        usbd_ep_send(&usbd_composite, CDC_ACM_IN_EP, data_send, iLength);
    }

    return iLength;
}

void vUsbSoftInit(void)
{
    /* USB device configuration */
    usbd_init(&usbd_composite, &hid_desc, &usb_class_composite);

    /* enabled USB pull-up */
    usbd_connect(&usbd_composite);
}
